package com.zhigh.project.security.jwt.authentication;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.zhigh.project.security.jwt.authentication.exception.JWTAuthenticationException;
import com.zhigh.project.tools.Assertions;
import com.zhigh.project.tools.RuntimeExceptions;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.context.support.MessageSourceAccessor;

import java.util.Date;
import java.util.Optional;

public class DefaultJWT implements JWT {

    private static final MessageSourceAccessor message = JWTMessageSource.getAccessor();

    private final JWTHeader header;

    private final JWTPayload payload;

    private final String tokenStr;

    private final String signature;

    public DefaultJWT(String tokenStr) {
        this(RuntimeExceptions.wrapper(
                () -> JWSObject.parse(tokenStr),
                e -> new JWTAuthenticationException("Parsing token to JWSObject failure.", e)
        ));
    }


    public DefaultJWT(DefaultJWTHeader header, JWTPayload payload, String secret) {
        this.header = header;
        this.payload = payload;

        Assertions.assertNonBlank(secret, () -> new JWTAuthenticationException("secret is blank."));

        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.parse(header.getAlg()))
                .type(JOSEObjectType.JWT).build();

        Payload jwsPayload = new Payload(payload.toPayloadStr());

        JWSObject jwsObject = new JWSObject(jwsHeader, jwsPayload);

        MACSigner jwsSigner = RuntimeExceptions.wrapper(
                () -> new MACSigner(secret),
                e -> new JWTAuthenticationException("Creating MACSigner failure.", e)
        );
        RuntimeExceptions.wrapper(
                () -> jwsObject.sign(jwsSigner),
                e -> new JWTAuthenticationException("Signing jwt failure.", e)
        );

        this.tokenStr = jwsObject.serialize();
        this.signature = jwsObject.getSignature().toString();
    }

    public DefaultJWT(JWSObject jwsObject) {

        Assertions.assertNonnull(jwsObject, () -> new JWTAuthenticationException("Signed-JWT is null."));

        this.header = RuntimeExceptions.wrapper(
                () -> new JSONObject(jwsObject.getHeader().toJSONObject()).toJavaObject(DefaultJWTHeader.class),
                e -> new JWTAuthenticationException("Parsing jwt's header is failure.", e)
        );

        this.payload = RuntimeExceptions.wrapper(
                () -> JSON.parseObject(jwsObject.getPayload().toBytes(), DefaultJWTPayload.class),
                e -> new JWTAuthenticationException("Parsing jwt's payload is failure.", e)
        );

        this.tokenStr = RuntimeExceptions.wrapper(
                () -> jwsObject.serialize(),
                e -> new JWTAuthenticationException("jwt is invalid", e)
        );

        this.signature = RuntimeExceptions.wrapper(
                () -> jwsObject.getSignature().toString(),
                e -> new JWTAuthenticationException("jwt is invalid", e)
        );

    }

    @Override
    public String getSignature() {
        return this.signature;
    }

    @Override
    public JWTHeader getHeader() {
        return header;
    }

    @Override
    public JWTPayload getPayload() {
        return payload;
    }

    @Override
    public String getTokenStr() {
        return tokenStr;
    }

    @Override
    public boolean verify(String secret) {
        Assertions.assertNonnull(tokenStr, () -> new JWTAuthenticationException(
                message.getMessage("jws.isnull", "JSON Web Signature Object is null")));

        JWSObject jwsObject = RuntimeExceptions.wrapper(
                () -> JWSObject.parse(tokenStr),
                e -> new JWTAuthenticationException("jwt is invalid", e)
        );

        Assertions.assertTrue(jwsObject.getState() == JWSObject.State.SIGNED,
                () -> new JWTAuthenticationException(message.getMessage("jws.verify.non-signed", "JSON Web Signature Object is non-signed.")));

        MACVerifier jwsVerifier = RuntimeExceptions.wrapper(
                () -> new MACVerifier(secret),
                e -> new JWTAuthenticationException("Creating MACVerifier failure.", e)
        );

        return RuntimeExceptions.wrapper(
                () -> jwsObject.verify(jwsVerifier),
                e -> new JWTAuthenticationException("Verifying jwt failure.", e)
        );
    }

    @Override
    public boolean isEnabled() {
        return isNonExpired() && Optional.ofNullable(getPayload()).map(JWTPayload::getNbf).orElse(0L).compareTo(new Date().getTime()) <= 0;
    }

    @Override
    public boolean isNonExpired() {
        Long expTime = Optional.ofNullable(getPayload()).map(JWTPayload::getExp).orElse(0L);
        return expTime == 0 || expTime.compareTo(new Date().getTime()) >= 0;
    }


    @Override
    public void eraseCredentials() {
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("header", header)
                .append("payload", payload)
                .append("tokenStr", tokenStr)
                .append("signature", signature)
                .toString();
    }
}
